﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Globalization;
using System.Security.Permissions;
using Microsoft.Practices.EnterpriseLibrary.Data;
using Microsoft.Practices.EnterpriseLibrary.Data.Instrumentation;
using Oracle.DataAccess.Client;
using Oracle.DataAccess.Types;

namespace PSE.Framework.Data.Oracle
{
    /// <summary>
    /// <para>Representa um Oracle database.</para>
    /// </summary>
    /// <remarks> 
    /// <para>
    /// Internamente use Oracle Database Provider .NET da Oracle <see cref="Oracle.DataAccess.Client"/> para conectar a banco Oracle 9i, ou superior.
    /// </para>  
    /// <para>
    /// Quando retornando um result set, ele irá contruir um package name. O package name should deverá ser baseado em uma
    /// stored procedure comum prefixo, e isto deverá ser configurado. Por exempllo, o nome do 
    /// package deverá ter como prefixo "ENTLIB_"  e o nome da package 
    /// "pkgENTLIB_ARCHITECTURE". Para aplicações, isto é requerido somente se você utilizar ref cursors.
    /// </para>
    /// </remarks>
    [OraclePermission(SecurityAction.Demand)]
    public class OracleDatabase : Database, IDisposable
    {
        #region Attributes

        private const string RefCursorName = "cur_OUT";
        private const int auto_size = 1024;
        private readonly IEnumerable<IOraclePackage> packages;
        private static readonly IEnumerable<IOraclePackage> emptyPackages = new List<IOraclePackage>(0);
        private readonly IDictionary<string, ParameterTypeRegistry> registeredParameterTypes = new Dictionary<string, ParameterTypeRegistry>();

        #endregion

        #region Constructors

        /// <summary>
        /// Inicializa uma nova instance da classe <see cref="OracleDatabase"/> com uma connection string.
        /// </summary>
        /// <param name="connectionString">A connection string para o banco de dados.</param>
        /// 
        public OracleDatabase(string connectionString)
            : this(connectionString, emptyPackages)
        {

        }

        /// <summary>
        /// Inicializa uma nova instance da classe <see cref="OracleDatabase"/> com uma connection string, e uma lista de Oracle packages.
        /// </summary>
        /// <param name="connectionString">A connection string para o banco de dados.</param>
        /// <param name="packages">Uma lista de objetos <see cref="IOraclePackage"/>.</param>
        public OracleDatabase(string connectionString, IEnumerable<IOraclePackage> packages)
            : this(connectionString, packages, new NullDataInstrumentationProvider())
        {

        }

        /// <summary>
        /// Inicializa uma nova instance da classe <see cref="OracleDatabase"/> com uma connection string, 
        /// uma lista de Oracle packages e um instrumentation provider.
        /// </summary>
        /// <param name="connectionString">A connection string para o banco de dados.</param>
        /// <param name="packages">Uma lista de objetos <see cref="IOraclePackage"/>.</param>
        /// <param name="instrumentationProvider">O instrumentation provider.</param>
        public OracleDatabase(string connectionString, IEnumerable<IOraclePackage> packages, IDataInstrumentationProvider instrumentationProvider)
            : base(connectionString, OracleClientFactory.Instance, instrumentationProvider)
        {
            if (packages == null) throw new ArgumentNullException("packages");

            this.packages = packages;
        }

        #endregion

        #region Override Methods

        /// <summary>
        /// Adiciona um novo parâmetro no comando.
        /// Os parâmetros devem estar na order que aparecem na definição da stored procedure.
        /// </summary>
        /// <param nome="command">O comando para adicionar o parâmetro.</param>
        /// <param nome="name">O nome do parâmetro.</param>
        /// <param nome="dbType">O tipo do parâmetro.</param>
        /// <param nome="size">O tamanho máximo da coluna.</param>
        /// <param nome="direction">A direção do parâmetro.</param>
        /// <param nome="nullable">Indica se o parâmetro pode ser nulo.</param>
        /// <param nome="precision">O número máximo de digitos utilizado para representar o valor.</param>
        /// <param nome="scale">O número de casas decimais do valor.</param>
        /// <param nome="sourceColumn">O nome da coluna mapeado para o DataSet e
        /// utilizado para carregar ou retornar o valor.</param>
        /// <param nome="sourceVersion">A versão do DataSet que será utilizada.</param>
        /// <param nome="value">O valor do parâmetro.</param>
        public override void AddParameter(DbCommand command, string name, DbType dbType, int size,
            ParameterDirection direction, bool nullable, byte precision, byte scale, string sourceColumn,
            DataRowVersion sourceVersion, object value)
        {
            if (dbType == DbType.Guid)
            {
                object convertedValue = ConvertGuidToByteArray(value);

                this.AddParameter((OracleCommand)command, name, OracleDbType.Raw, 16, direction, nullable, precision,
                    scale, sourceColumn, sourceVersion, convertedValue);

                this.RegisterParameterType(command, name, dbType);
            }
            else
            {
                base.AddParameter(command, name, this.TransformDbType(dbType), size, direction, nullable, precision, scale,
                    sourceColumn, sourceVersion,
                    value is bool || value is bool ? Convert.ToInt16(value) : value);
            }
        }

        /// <summary>
        /// Executa o comando (wrapper) e retorna um IDataReader. 
        /// O DataReader deverá ser fechado por quem chamou este método.
        /// </summary>
        /// <param nome="command">O comando a ser executado.</param>
        /// <returns>O DataReader.</returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="command"/> não pode ser <see langword="null"/> (Nothing em Visual Basic).</para>
        /// </exception>
        public override IDataReader ExecuteReader(DbCommand command)
        {
            PrepareCWRefCursor(command);
            return new OracleDataReaderWrapper(base.ExecuteReader(command));
        }

        /// <summary>
        /// Todos os data readers são obtidos de objetos que gerenciam as coneões do banco de dados.
        /// Alguns vezes você irá necessitar criar um diferente wrapper, 
        /// neste caso, utilize este método.
        /// </summary>
        /// <param name="connection">Conexão que irá conter o Data Reader.</param>
        /// <param name="innerReader">o Data Reader para 'to wrap'.</param>
        /// <returns>The new reader.</returns>
        protected override IDataReader CreateWrappedReader(DatabaseConnectionWrapper connection, IDataReader innerReader)
        {
            return new RefCountingOracleDataReaderWrapper(connection, (OracleDataReader)innerReader);
        }

        /// <summary>
        /// Executa o comando e retorna um IDataReader.
        /// O DataReader deverá ser fechado por quem chamou este método.
        /// </summary>
        /// <param nome="command">O comando wrapper a ser executado.</param>
        /// <param nome="transaction">A transação na qual este comando será executado.</param>
        /// <returns>O DataReader.</returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="command"/> não pode ser <see langword="null"/> (Nothing em Visual Basic).</para>
        /// <para>- or -</para>
        /// <para><paramref name="transaction"/> não pode ser <see langword="null"/> (Nothing em Visual Basic).</para>
        /// </exception>
        public override IDataReader ExecuteReader(DbCommand command, DbTransaction transaction)
        {
            PrepareCWRefCursor(command);
            return new OracleDataReaderWrapper((OracleDataReader)base.ExecuteReader(command, transaction));
        }

        /// <summary>
        /// <para>Executa o comando e returna o resultado em um povo  <see cref="DataSet"/>.</para>
        /// </summary>
        /// <param name="command"><para>O comando executado para preencher o <see cref="DataSet"/></para></param>
        /// <returns><para>Um <see cref="DataSet"/> preenchido com registros, ou schema.</para></returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="command"/> não pode ser <see langword="null"/> (Nothing em Visual Basic).</para>
        /// </exception>
        public override DataSet ExecuteDataSet(DbCommand command)
        {
            PrepareCWRefCursor(command);
            return base.ExecuteDataSet(command);
        }

        /// <summary>
        /// <para>Executa o comando e returna o resultado em um povo  <see cref="DataSet"/>.</para>
        /// </summary>
        /// <param name="command"><para>O comando executado para preencher o <see cref="DataSet"/></para></param>
        /// <param name="transaction"><para>A transação utilizada na execuçao deste reader.</para></param>        
        /// <returns><para>Um <see cref="DataSet"/> preenchido com registros, ou schema.</para></returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="command"/> não pode ser <see langword="null"/> (<b>Nothing</b> in Visual Basic).</para>
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="command"/> não pode ser <see langword="null"/> (<b>Nothing</b> in Visual Basic).</para>
        /// <para>- or -</para>
        /// <para><paramref name="transaction"/> não pode ser <see langword="null"/> (<b>Nothing</b> in Visual Basic).</para>
        /// </exception>
        public override DataSet ExecuteDataSet(DbCommand command, DbTransaction transaction)
        {
            PrepareCWRefCursor(command);
            return base.ExecuteDataSet(command, transaction);
        }

        /// <summary>
        /// <para>Lê um <see cref="DataSet"/> de um <see cref="DbCommand"/> em uma transação.</para>
        /// </summary>
        /// <param name="command"><para>O comando executado para preencher o <see cref="DataSet"/>.</para></param>
        /// <param name="dataSet"><para>O <see cref="DataSet"/> para preencher.</para></param>
        /// <param name="tableNames"><para>Um array de nomes de tabelas para o <see cref="DataSet"/>.</para></param>
        public override void LoadDataSet(DbCommand command, DataSet dataSet, string[] tableNames)
        {
            PrepareCWRefCursor(command);
            base.LoadDataSet(command, dataSet, tableNames);
        }

        /// <summary>
        /// <para>Lê um <see cref="DataSet"/> de um <see cref="DbCommand"/> em uma transação.</para>
        /// </summary>
        /// <param name="command"><para>O comando executado para preencher o <see cref="DataSet"/>.</para></param>
        /// <param name="dataSet"><para>O <see cref="DataSet"/> para preencher.</para></param>
        /// <param name="tableNames"><para>Um array de nomes de tabelas para o <see cref="DataSet"/>.</para></param>
        /// <param name="transaction"><para>O <see cref="IDbTransaction"/> no qual se executará o comando.</para></param>
        public override void LoadDataSet(DbCommand command, DataSet dataSet, string[] tableNames, DbTransaction transaction)
        {
            PrepareCWRefCursor(command);
            base.LoadDataSet(command, dataSet, tableNames, transaction);
        }

        /// <summary>
        /// Recupera o valor do parâmetro, após a execução do comando.
        /// </summary>
        /// <param nome="command">O comando que contém o parâmetro.</param>
        /// <param nome="parameterName">O nome do parâmetro.</param>
        /// <returns>O valor do parâmetro.</returns>
        public override object GetParameterValue(DbCommand command, string parameterName)
        {
            if (command == null) throw new ArgumentNullException("command");

            object convertedValue = base.GetParameterValue(command, parameterName);

            ParameterTypeRegistry registry = GetParameterTypeRegistry(command.CommandText);
            if (registry != null)
            {
                if (registry.HasRegisteredParameterType(parameterName))
                {
                    DbType dbType = registry.GetRegisteredParameterType(parameterName);

                    if (DbType.Guid == dbType)
                    {
                        convertedValue = ConvertByteArrayToGuid(convertedValue);
                    }
                    else if (DbType.Boolean == dbType)
                    {
                        convertedValue = Convert.ToBoolean(convertedValue, CultureInfo.InvariantCulture);
                    }
                }
            }

            return convertedValue;
        }

        /// <summary>
        /// Atribui um valor ao parâmetro do comando.
        /// </summary>
        /// <param nome="command">O comando que contém o parâmetro.</param>
        /// <param nome="parameterName">O nome do parâmetro.</param>
        /// <param nome="value">O valor do parâmetro.</param>
        public override void SetParameterValue(DbCommand command, string parameterName, object value)
        {
            if (command == null) throw new ArgumentNullException("command");

            object convertedValue = value;

            ParameterTypeRegistry registry = GetParameterTypeRegistry(command.CommandText);
            if (registry != null)
            {
                if (registry.HasRegisteredParameterType(parameterName))
                {
                    DbType dbType = registry.GetRegisteredParameterType(parameterName);

                    if (DbType.Guid == dbType)
                    {
                        convertedValue = ConvertGuidToByteArray(value);
                    }
                }
            }

            base.SetParameterValue(command, parameterName, convertedValue);
        }

        /// <summary>
        /// Verificação se o banco de dados <see cref='Database'/> , ou provider, 
        /// suporta a descoberta de parâmetros de stored procedure ou finctions.
        /// </summary>
        /// <value>true.</value>
        public override bool SupportsParemeterDiscovery
        {
            get { return true; }
        }

        /// <summary>
        /// Recupera informações sobre parâmetros de stored procedure, ou functions, 
        /// especificados no <see cref="DbCommand"/> e popula a coleção de Parâmetros
        /// do especificado objeto <see cref="DbCommand"/>. 
        /// </summary>
        /// <param name="discoveryCommand">O <see cref="DbCommand"/> para fazer a descoberta.</param>
        /// <remarks>
        /// O <see cref="DbCommand"/> deve ser uma instância de um <see cref="OracleCommand"/> object.
        /// </remarks>
        protected override void DeriveParameters(DbCommand discoveryCommand)
        {
            OracleCommandBuilder.DeriveParameters((OracleCommand)discoveryCommand);
        }

        /// <summary>
        /// <para>Cria um <see cref="DbCommand"/> para uma stored procedure.</para>
        /// </summary>
        /// <param name="storedProcedureName"><para>O nome da stored procedure.</para></param>
        /// <param name="parameterValues"><para>A lista dos valores dos parâmetros para a procedure.</para></param>
        /// <returns><para>O <see cref="DbCommand"/> para a stored procedure.</para></returns>
        /// <remarks>
        /// <para>Os parâmetros descobertos para a stored procedure e seus valores devem estar na ordem posicional.</para>
        /// </remarks>        
        public override DbCommand GetStoredProcCommand(string storedProcedureName, params object[] parameterValues)
        {
            // need to do this before of eventual parameter discovery
            string updatedStoredProcedureName = TranslatePackageSchema(storedProcedureName);
            DbCommand command = baseGetStoredProcCommand(updatedStoredProcedureName, true, parameterValues);
            return command;
        }

        /// <summary>
        /// <para>Descobre os parâmetros do <paramref name="command"/> e 
        /// associa os valores do <paramref name="parameterValues"/> para o <paramref name="command"/>s Parameters list.</para>
        /// </summary>
        /// <param name="command">O comando para o qual os valores dos parâmetros irão se associados.</param>
        /// <param name="parameterValues">Os valores dos parâmetros que serão associados ao comando.</param>
        public override void AssignParameters(DbCommand command, object[] parameterValues)
        {
            if (command == null) throw new ArgumentNullException("command");

            // need to do this before of eventual parameter discovery
            string updatedStoredProcedureName = TranslatePackageSchema(command.CommandText);

            base.AssignParameters(command, parameterValues);
        }

        /// <summary>
        /// Cria uma instância da classe <see cref="DbCommand"/> para uma stored procedure.
        /// Os parâmetros devem estar na order que aparecem na definição da stored procedure.
        /// </summary>
        /// <param nome="storedProcedureName">O nome da stored procedure.</param>
        /// <returns>O comando para a stored procedure.</returns>
        /// <remarks><para>Os parâmetros da stored procedure irão ser descobertos e 
        /// seus valores serão associados na ordem posicional.</para></remarks>  
        public override DbCommand GetStoredProcCommand(string storedProcedureName)
        {
            // need to do this before of eventual parameter discovery
            string updatedStoredProcedureName = TranslatePackageSchema(storedProcedureName);
            DbCommand command = baseGetStoredProcCommand(updatedStoredProcedureName, true);
            return command;
        }

        /// <summary>
        /// Cria uma instância da classe <see cref="DbCommand"/> para uma stored procedure.
        /// Os parâmetros devem estar na order que aparecem na definição da stored procedure.
        /// </summary>
        /// <param nome="storedProcedureName">O nome da stored procedure.</param>
        /// <param nome="commandTimeout">O Timeout utilizado na execução do comando.</param>
        /// <returns>O comando para a stored procedure.</returns>
        /// <remarks><para>Os parâmetros da stored procedure irão ser descobertos e 
        /// seus valores serão associados na ordem posicional.</para></remarks>  
        public DbCommand GetStoredProcCommand(string storedProcedureName, int commandTimeout)
        {
            // need to do this before of eventual parameter discovery
            string updatedStoredProcedureName = TranslatePackageSchema(storedProcedureName);
            DbCommand command = baseGetStoredProcCommand(updatedStoredProcedureName, true);
            command.CommandTimeout = commandTimeout;
            return command;
        }

        /// <summary>
        /// Cria uma instância da classe <see cref="DbCommand"/> para uma stored procedure.
        /// Os parâmetros devem estar na order que aparecem na definição da stored procedure.
        /// </summary>
        /// <param nome="storedProcedureName">O nome da stored procedure.</param>
        /// <param nome="commandTimeout">O Timeout utilizado na execução do comando.</param>
        /// <param nome="sourceColumns">Os valores dos parâmetros da stored procedure. 
        /// Os parâmetros devem estar na order que aparecem na definição da stored procedure.</param>
        /// <returns>O comando para a stored procedure.</returns>
        /// <remarks><para>Os parâmetros da stored procedure irão ser descobertos e 
        /// seus valores serão associados na ordem posicional.</para></remarks>  
        public DbCommand GetStoredProcCommandWithSourceColumns(string storedProcedureName, int commandTimeout, string[] sourcesColumns)
        {
            // need to do this before of eventual parameter discovery
            string updatedStoredProcedureName = TranslatePackageSchema(storedProcedureName);
            DbCommand command = baseGetStoredProcCommandWithSourceColumns(storedProcedureName, sourcesColumns, true);
            command.CommandTimeout = commandTimeout;
            return command;
        }

        /// <summary>
        /// Configura o evento RowUpdated para o Data Adapter (<see cref="DbDataAdapter"/>).
        /// </summary>
        /// <param name="adapter">O <see cref="DbDataAdapter"/> que disparará o evento.</param>
        /// <remarks>O <see cref="DbDataAdapter"/> deve ser um <see cref="OracleDataAdapter"/>.</remarks>
        protected override void SetUpRowUpdatedEvent(DbDataAdapter adapter)
        {
            ((OracleDataAdapter)adapter).RowUpdated += OnOracleRowUpdated;
        }

        #endregion

        #region Private Static Methods

        /// <summary>
        /// Checks if oracle command.
        /// </summary>
        /// <param name="command">The command.</param>
        /// <returns></returns>
        private static void CheckIfOracleCommand(DbCommand command)
        {
            OracleCommand oracleCommand = (IDbCommand)command as OracleCommand;
            if (oracleCommand == null)
                throw new ArgumentException("ExceptionCommandNotOracleCommand");
        }

        private static object ConvertGuidToByteArray(object value)
        {
            return ((value is DBNull) || (value == null)) ? Convert.DBNull : ((Guid)value).ToByteArray();
        }

        private static object ConvertByteArrayToGuid(object value)
        {
            byte[] buffer = (byte[])value;
            if (buffer.Length == 0)
            {
                return DBNull.Value;
            }
            else
            {
                return new Guid(buffer);
            }
        }

        private static bool QueryProcedureNeedsCursorParameter(DbCommand command)
        {
            foreach (OracleParameter parameter in command.Parameters)
            {
                if (parameter.OracleDbType == OracleDbType.RefCursor)
                {
                    return false;
                }
            }
            return true;
        }

        /// <devdoc>
        /// Looks into configuration and gets the information on how the command wrapper 
        /// should be updated if calling a package on this connection.
        /// </devdoc>        
        private string TranslatePackageSchema(string storedProcedureName)
        {
            const string allPrefix = "*";
            string packageName = String.Empty;
            string updatedStoredProcedureName = storedProcedureName;

            if (packages != null && !string.IsNullOrEmpty(storedProcedureName))
            {
                foreach (IOraclePackage oraPackage in packages)
                {
                    if ((oraPackage.Prefix == allPrefix) || (storedProcedureName.StartsWith(oraPackage.Prefix)))
                    {
                        //use the package name for the matching prefix
                        packageName = oraPackage.Name;
                        //prefix = oraPackage.Prefix;
                        break;
                    }
                }
            }
            if (0 != packageName.Length)
            {
                updatedStoredProcedureName = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", packageName, storedProcedureName);
            }

            return updatedStoredProcedureName;
        }

        #endregion

        #region Private Methods

        /// <devdoc>
        /// This is a private method that will build the Oracle package name if your stored procedure
        /// has proper prefix and postfix. 
        /// This functionality is include for
        /// the portability of the architecture between SQL and Oracle datbase.
        /// This method also adds the reference cursor to the command writer if not already added. This
        /// is required for Oracle .NET managed data provider.
        /// </devdoc>        
        private void PrepareCWRefCursor(DbCommand command)
        {
            if (command == null) throw new ArgumentNullException("command");

            if (CommandType.StoredProcedure == command.CommandType)
            {
                // Check for ref. cursor in the command writer, if it does not exist, 
                // add a know reference cursor out of "cur_OUT"
                if (QueryProcedureNeedsCursorParameter(command))
                {
                    AddParameter(command as OracleCommand, RefCursorName, OracleDbType.RefCursor, 0, ParameterDirection.Output, true, 0, 0, String.Empty, DataRowVersion.Default, Convert.DBNull);
                }
            }
        }

        private ParameterTypeRegistry GetParameterTypeRegistry(string commandText)
        {
            ParameterTypeRegistry registry;
            registeredParameterTypes.TryGetValue(commandText, out registry);
            return registry;
        }

        private void RegisterParameterType(DbCommand command, string parameterName, DbType dbType)
        {
            ParameterTypeRegistry registry = GetParameterTypeRegistry(command.CommandText);
            if (registry == null)
            {
                registry = new ParameterTypeRegistry(command.CommandText);
                registeredParameterTypes.Add(command.CommandText, registry);
            }

            registry.RegisterParameterType(parameterName, dbType);
        }

        /// <devdoc>
        /// Listens for the RowUpdate event on a data adapter to support UpdateBehavior.Continue
        /// </devdoc>
        private void OnOracleRowUpdated(object sender, OracleRowUpdatedEventArgs args)
        {
            if (args.RecordsAffected == 0)
            {
                if (args.Errors != null)
                {
                    args.Row.RowError = "Failed to update row";
                    args.Status = UpdateStatus.SkipCurrentRow;
                }
            }
        }

        private OracleParameterStatus[] convertToOracleParameterStatus(DbParameterStatus[] ArrayBindStatus)
        {
            if (ArrayBindStatus == null) return null;
            OracleParameterStatus[] _ArrayBindStatus = new OracleParameterStatus[ArrayBindStatus.GetLength(0)];
            for (int i = 0; i < ArrayBindStatus.GetLength(0); ++i) _ArrayBindStatus[i] = (OracleParameterStatus)ArrayBindStatus[i];
            return _ArrayBindStatus;
        }

        private void SetArraySize(OracleCommand command, int int_size)
        {
            for (int i = 0; i < command.Parameters.Count; ++i)
            {
                if (command.Parameters[i].CollectionType == OracleCollectionType.PLSQLAssociativeArray)
                {
                    command.Parameters[i].Size = int_size;
                    command.Parameters[i].ArrayBindSize = new int[int_size];

                    for (int j = 0; j < int_size; ++j)
                    {
                        switch (command.Parameters[i].DbType)
                        {
                            case DbType.AnsiString:
                            case DbType.AnsiStringFixedLength:
                            case DbType.Binary:
                                command.Parameters[i].ArrayBindSize[j] = 8000;
                                break;
                            case DbType.Boolean:
                            case DbType.Byte:
                                command.Parameters[i].ArrayBindSize[j] = 2;
                                break;
                            case DbType.Int16:
                            case DbType.Int32:
                            case DbType.Int64:
                            case DbType.Currency:
                            case DbType.Decimal:
                            case DbType.Double:
                            case DbType.UInt16:
                            case DbType.UInt32:
                            case DbType.UInt64:
                            case DbType.VarNumeric:
                                command.Parameters[i].ArrayBindSize[j] = 16;
                                break;
                            case DbType.Date:
                            case DbType.DateTime:
                            case DbType.DateTime2:
                            case DbType.DateTimeOffset:
                            case DbType.Time:
                                command.Parameters[i].ArrayBindSize[j] = 64;
                                break;
                            case DbType.Guid:
                                command.Parameters[i].ArrayBindSize[j] = 128;
                                break;
                            case DbType.String:
                            case DbType.StringFixedLength:
                                command.Parameters[i].ArrayBindSize[j] = 4000;
                                break;
                            case DbType.Xml:
                                command.Parameters[i].ArrayBindSize[j] = 4000;
                                break;
                            default:
                                command.Parameters[i].ArrayBindSize[j] = 4000;
                                break;
                        }
                    }
                }
            }
        }

        private DbType TransformDbType(DbType dbType)
        {
            DbType _dbType = dbType;
            if (dbType == DbType.Boolean) _dbType = DbType.Int16;
            if (dbType == DbType.SByte) _dbType = DbType.Int16;
            if (dbType == DbType.UInt16) _dbType = DbType.Int32;
            if (dbType == DbType.UInt32) _dbType = DbType.Int64;
            if (dbType == DbType.UInt64) _dbType = DbType.Int64;
            if (dbType == DbType.Currency) _dbType = DbType.Decimal;
            if (dbType == DbType.VarNumeric) _dbType = DbType.Decimal;
            if (dbType == DbType.AnsiString) _dbType = DbType.String;
            if (dbType == DbType.AnsiStringFixedLength) _dbType = DbType.String;
            if (dbType == DbType.DateTime2) _dbType = DbType.DateTime;
            if (dbType == DbType.DateTimeOffset) _dbType = DbType.DateTime;
            if (dbType == DbType.Xml) _dbType = DbType.String;
            return _dbType;
        }

        #endregion

        #region PL/SQL Associative Array

        /// <summary>
        /// Adiciona um novo parâmetro INPUT PL/SQL Associative Array no comando.
        /// </summary>
        /// <param nome="command">O comando para adicionar o parâmetro.</param>
        /// <param nome="name">O nome do parâmetro.</param>
        /// <param nome="dbType">O tipo do parâmetro.</param>
        /// <param nome="ArrayValue">Um Array com os valores do parâmetro.</param>
        /// <param nome="Size">O número máximo de elementos no PL/SQL Associative Array.</param>
        /// <param nome="ArrayBindSize">Array com os tamanhos dos valores do parâmetro.</param>
        /// <param nome="ArrayBindStatus">Array com os Status do parâmetro.</param>
        public void AddInParameterAssociativeArray(DbCommand command, string name, DbType dbType, Array ArrayValue,
                                                   int Size, int[] ArrayBindSize, DbParameterStatus[] ArrayBindStatus)
        {
            DbParameter op = CreateParameter(name);
            op.DbType = TransformDbType(dbType);
            op.Direction = ParameterDirection.Input;
            op.Value = ArrayValue;
            op.Size = Size;
            ((OracleParameter)op).ArrayBindSize = ArrayBindSize;
            ((OracleParameter)op).ArrayBindStatus = convertToOracleParameterStatus(ArrayBindStatus);
            ((OracleParameter)op).CollectionType = OracleCollectionType.PLSQLAssociativeArray;
            command.Parameters.Add(op);
        }
        /// <summary>
        /// Adiciona um novo parâmetro OUTPUT PL/SQL Associative Array no comando.
        /// </summary>
        /// <param nome="command">O comando para adicionar o parâmetro.</param>
        /// <param nome="name">O nome do parâmetro.</param>
        /// <param nome="dbType">O tipo do parâmetro.</param>
        /// <param nome="ArrayValue">Um Array com os valores do parâmetro.</param>
        /// <param nome="Size">O número máximo de elementos no PL/SQL Associative Array.</param>
        /// <param nome="ArrayBindSize">Array com os tamanhos dos valores do parâmetro.</param>
        /// <param nome="ArrayBindStatus">Array com os Status do parâmetro.</param>
        public void AddOutParameterAssociativeArray(DbCommand command, string name, DbType dbType, int Size,
                                                    int[] ArrayBindSize, DbParameterStatus[] ArrayBindStatus)
        {
            DbParameter op = CreateParameter(name);
            op.DbType = TransformDbType(dbType);
            op.Direction = ParameterDirection.Output;
            op.Value = null;
            op.Size = Size;
            ((OracleParameter)op).CollectionType = OracleCollectionType.PLSQLAssociativeArray;
            ((OracleParameter)op).ArrayBindSize = ArrayBindSize;
            ((OracleParameter)op).ArrayBindStatus = convertToOracleParameterStatus(ArrayBindStatus);
            command.Parameters.Add(op);
        }
        /// <summary>
        /// Adiciona um novo parâmetro PL/SQL Associative Array no comando.
        /// </summary>
        /// <param nome="command">O comando para adicionar o parâmetro.</param>
        /// <param nome="name">O nome do parâmetro.</param>
        /// <param nome="dbType">O tipo do parâmetro.</param>
        /// <param nome="direction">A direção do parâmetro.</param>
        /// <param nome="ArrayValue">Array com os valores do parâmetro.</param>
        /// <param nome="Size">O número máximo de elementos no PL/SQL Associative Array.</param>
        /// <param nome="ArrayBindSize">Array com os tamanhos dos valores do parâmetro.</param>
        public void AddParameterAssociativeArray(DbCommand command, string name, DbType dbType,
                                                 ParameterDirection direction, Array ArrayValue, int Size, int[] ArrayBindSize)
        {
            DbParameter op1 = CreateParameter(name);
            op1.DbType = TransformDbType(dbType);
            op1.Direction = direction;
            op1.Value = ArrayValue;
            op1.Size = Size;
            ((OracleParameter)op1).CollectionType = OracleCollectionType.PLSQLAssociativeArray;
            ((OracleParameter)op1).ArrayBindSize = ArrayBindSize;
            OracleParameterStatus[] arr_ops = new OracleParameterStatus[ArrayValue.GetLength(0)];
            for (int i = 0; i < arr_ops.GetLength(0); i++) arr_ops[i] = OracleParameterStatus.Success;
            ((OracleParameter)op1).ArrayBindStatus = arr_ops;
            command.Parameters.Add(op1);
        }
        /// <summary>
        /// Adiciona um novo parâmetro PL/SQL Associative Array no comando.
        /// </summary>
        /// <param nome="command">O comando para adicionar o parâmetro.</param>
        /// <param nome="name">O nome do parâmetro.</param>
        /// <param nome="dbType">O tipo do parâmetro.</param>
        /// <param nome="direction">A direção do parâmetro.</param>
        /// <param nome="ArrayValue">Um Array com os valores do parâmetro.</param>
        /// <param nome="Size">O número máximo de elementos no PL/SQL Associative Array.</param>
        /// <param nome="ArrayBindSize">Array com os tamanhos dos valores do parâmetro.</param>
        /// <param nome="ArrayBindStatus">Array com os Status do parâmetro.</param>
        public void AddParameterAssociativeArray(DbCommand command, string name, DbType dbType, ParameterDirection direction,
                                                 Array ArrayValue, int Size, int[] ArrayBindSize, DbParameterStatus[] ArrayBindStatus)
        {
            DbParameter op = CreateParameter(name);
            op.DbType = TransformDbType(dbType);
            op.Direction = direction;
            op.Value = null;
            op.Size = Size;
            ((OracleParameter)op).CollectionType = OracleCollectionType.PLSQLAssociativeArray;
            ((OracleParameter)op).ArrayBindSize = ArrayBindSize;
            ((OracleParameter)op).ArrayBindStatus = convertToOracleParameterStatus(ArrayBindStatus);
            command.Parameters.Add(op);
        }
        /// <summary>
        /// Adiciona um novo parâmetro PL/SQL Associative Array no comando.
        /// </summary>
        /// <param nome="command">O comando para adicionar o parâmetro.</param>
        /// <param nome="name">O nome do parâmetro.</param>
        /// <param nome="dbType">O tipo do parâmetro.</param>
        /// <param nome="direction">A direção do parâmetro.</param>
        /// <param nome="ArrayValue">Array com os valores do parâmetro.</param>
        /// <param nome="Size">O número máximo de elementos no PL/SQL Associative Array.</param>
        /// <param nome="ArrayBindSize">Array com os tamanhos dos valores do parâmetro.</param>
        public void SetParameterAssociativeArray(DbCommand command, int Index, Array ArrayValue, int Size, int[] ArrayBindSize)
        {
            command.Parameters[Index].Value = ArrayValue;                  // Setup the values for PL/SQL Associative Array
            command.Parameters[Index].Size = Size;                         // Specify the maximum number of elements in the PL/SQL Associative Array
            ((OracleParameter)command.Parameters[Index]).ArrayBindSize = ArrayBindSize;       // Setup the ArrayBindSize for Param1
        }

        /// <summary>
        /// Adiciona um novo parâmetro PL/SQL Associative Array no comando.
        /// </summary>
        /// <param nome="command">O comando para adicionar o parâmetro.</param>
        /// <param nome="name">O nome do parâmetro.</param>
        /// <param nome="dbType">O tipo do parâmetro.</param>
        /// <param nome="direction">A direção do parâmetro.</param>
        /// <param nome="ArrayValue">Array com os valores do parâmetro.</param>
        /// <param nome="Size">O número máximo de elementos no PL/SQL Associative Array.</param>
        /// <param nome="ArrayBindSize">Array com os tamanhos dos valores do parâmetro.</param>
        public void SetParameterAssociativeArray(DbCommand command, string name, Array ArrayValue, int Size, int[] ArrayBindSize)
        {
            command.Parameters[name].Value = ArrayValue;                  // Setup the values for PL/SQL Associative Array
            command.Parameters[name].Size = Size;                         // Specify the maximum number of elements in the PL/SQL Associative Array
            ((OracleParameter)command.Parameters[name]).ArrayBindSize = ArrayBindSize;       // Setup the ArrayBindSize for Param1
        }

        #endregion

        #region Array Bind

        /// <summary>
        /// Adiciona um novo parâmetro no comando.
        /// Os parâmetros devem estar na order que aparecem na definição da stored procedure ou CommandText.
        /// </summary>
        /// <param nome="command">O comando para adicionar o parâmetro de entrada.</param>
        /// <param nome="name">O nome do parâmetro.</param>
        /// <param nome="dbType">O tipo do parâmetro.</param>
        /// <param nome="size">O tamanho máximo da coluna.</param>
        /// <param nome="direction">A direção do parâmetro.</param>
        /// <param nome="nullable">Indica se o parâmetro pode ser nulo.</param>
        /// <param nome="precision">O número máximo de digitos utilizado para representar o valor.</param>
        /// <param nome="scale">O número de casas decimais do valor.</param>
        /// <param nome="sourceColumn">O nome da coluna mapeado para o DataSet e
        /// utilizado para carregar ou retornar o valor.</param>
        /// <param nome="sourceVersion">A versão do DataSet que será utilizada.</param>
        /// <param nome="value">Um Array com os valores do parâmetro.</param>
        /// <remarks>
        /// O Array para o valor de cada parâmetro de entrada deve ter 
        /// dimensão igual a propriedade ArrayBindCount do comando utilizado.
        /// </remarks>
        public void AddParameterBind(DbCommand command, string name, DbType dbType, int size, ParameterDirection direction,
                                     bool nullable, byte precision, byte scale, string sourceColumn, DataRowVersion sourceVersion, Array value)
        {
            if (command == null) throw new ArgumentNullException("command");

            OracleParameter param = base.CreateParameter(name, DbType.AnsiString, size, direction, nullable, precision, scale, sourceColumn, sourceVersion, value) as OracleParameter;
            param.DbType = dbType;
            command.Parameters.Add(param);
        }

        /// <summary>
        /// Adiciona um novo parâmetro no comando.
        /// Os parâmetros devem estar na order que aparecem na definição da stored procedure ou CommandText.
        /// </summary>
        /// <param nome="command">O comando para adicionar o parâmetro de entrada.</param>
        /// <param nome="name">O nome do parâmetro.</param>
        /// <param nome="direction">A direção do parâmetro.</param>
        /// <param nome="nullable">Indica se o parâmetro pode ser nulo.</param>
        /// <param nome="precision">O número máximo de digitos utilizado para representar o valor.</param>
        /// <param nome="scale">O número de casas decimais do valor.</param>
        /// <param nome="sourceColumn">O nome da coluna mapeado para o DataSet e
        /// utilizado para carregar ou retornar o valor.</param>
        /// <param nome="sourceVersion">A versão do DataSet que será utilizada.</param>
        /// <param nome="value">Um Array com os valores do parâmetro.</param>
        /// <remarks>
        /// O Array para o valor de cada parâmetro de entrada deve ter 
        /// dimensão igual a propriedade ArrayBindCount do comando utilizado.
        /// </remarks>
        public void AddParameterBindCursor(DbCommand command, string name, ParameterDirection direction,
                                           bool nullable, byte precision, byte scale, string sourceColumn, DataRowVersion sourceVersion, Array value)
        {
            if (command == null) throw new ArgumentNullException("command");

            OracleParameter param = base.CreateParameter(name, DbType.AnsiString, 0, direction, nullable, precision, scale, sourceColumn, sourceVersion, value) as OracleParameter;
            param.OracleDbType = OracleDbType.RefCursor;
            command.Parameters.Add(param);
        }

        /// <summary>
        /// Executa o comando do tipo ArrayBind e retorna o número de linhas afetadas.
        /// </summary>
        /// <param nome="command">O comando a ser executado.</param>
        /// <param nome="arrayBindCount">O número de vezes que será feita a execução, 
        /// ou seja, a dimensão dos parâmetros do comando.</param>
        /// <returns>O número de linhas afetadas.</returns>
        /// <remarks>
        /// O Array para o valor de cada parâmetro de entrada deve ter 
        /// dimensão igual a propriedade ArrayBindCount deste comando.
        /// </remarks>
        public void ExecuteNonQueryArrayBind(DbCommand command, int arrayBindCount)
        {
            ((OracleCommand)command).ArrayBindCount = arrayBindCount;
            CheckIfOracleCommand(command);

            using (DatabaseConnectionWrapper wrapper = this.GetOpenConnection())
            {
                PrepareCommand(command, wrapper.Connection);
                command.ExecuteNonQuery();
            }
        }

        /// <summary>
        /// Executa o comando do tipo ArrayBind e retorna o número de linhas afetadas.
        /// </summary>
        /// <param nome="command">O comando a ser executado.</param>
        /// <param nome="arrayBindCount">O número de vezes que será feita a execução, 
        /// ou seja, a dimensão dos parâmetros do comando.</param>
        /// <param nome="ArrayBindErrors">Um Array representando a coleção de erros, 
        /// caso haja erro na execução do comando.</param>
        /// <returns>O número de linhas afetadas.</returns>
        /// <remarks>
        /// O Array para o valor de cada parâmetro de entrada deve ter 
        /// dimensão igual a propriedade ArrayBindCount deste comando.
        /// </remarks>
        public void ExecuteNonQueryArrayBind(DbCommand command, int arrayBindCount, out Dictionary<int, string> ArrayBindErrors)
        {
            ArrayBindErrors = new Dictionary<int, string>();
            ((OracleCommand)command).ArrayBindCount = arrayBindCount;
            CheckIfOracleCommand(command);

            try
            {
                using (DatabaseConnectionWrapper wrapper = this.GetOpenConnection())
                {
                    PrepareCommand(command, wrapper.Connection);
                    command.ExecuteNonQuery();
                }
            }
            catch (OracleException e)
            {
                if (e.Number == 24381)
                    for (int i = 0; i < e.Errors.Count; i++)
                        ArrayBindErrors.Add(e.Errors[i].ArrayBindIndex, e.Errors[i].Message);
            }

        }

        /// <summary>
        /// Cria um comando do tipo ArrayBind, a partir de expressão SQL.
        /// </summary>
        /// <param nome="query">O texto da expressão SQL.</param>
        /// <param nome="arrayBindCount">O número de vezes que será feita a execução, 
        /// ou seja, a dimensão dos parâmetros do comando.</param>
        /// <returns>O comando para a consulta SQL.</returns>
        public DbCommand GetSqlStringCommandArrayBind(string query, int arrayBindCount)
        {
            return GetSqlStringCommandArrayBind(query, 0, arrayBindCount, true);
        }

        /// <summary>
        /// Cria um comando do tipo ArrayBind, a partir de expressão SQL.
        /// </summary>
        /// <param nome="query">O texto da expressão SQL.</param>
        /// <param nome="arrayBindCount">O número de vezes que será feita a execução, 
        /// ou seja, a dimensão dos parâmetros do comando.</param>
        /// <param nome="bindByName">Permite a adição desordenada dos parâmetros, a custa de performance.</param>
        /// <returns>O comando para a consulta SQL.</returns>
        public DbCommand GetSqlStringCommandArrayBind(string query, int arrayBindCount, bool bindByName)
        {
            return GetSqlStringCommandArrayBind(query, 0, arrayBindCount, bindByName);
        }

        /// <summary>
        /// Cria um comando do tipo ArrayBind, a partir de expressão SQL.
        /// </summary>
        /// <param nome="query">O texto da expressão SQL.</param>
        /// <param nome="commandTimeout">O Timeout utilizado na execução do comando.</param>
        /// <param nome="arrayBindCount">O número de vezes que será feita a execução, 
        /// ou seja, a dimensão dos parâmetros do comando.</param>
        /// <returns>O comando para a consulta SQL.</returns>
        public DbCommand GetSqlStringCommandArrayBind(string query, int commandTimeout, int arrayBindCount)
        {
            return GetSqlStringCommandArrayBind(query, commandTimeout, arrayBindCount, true);
        }

        /// <summary>
        /// Cria um comando do tipo ArrayBind, a partir de expressão SQL.
        /// </summary>
        /// <param nome="query">O texto da expressão SQL.</param>
        /// <param nome="commandTimeout">O Timeout utilizado na execução do comando.</param>
        /// <param nome="arrayBindCount">O número de vezes que será feita a execução, 
        /// ou seja, a dimensão dos parâmetros do comando.</param>
        /// <param nome="bindByName">Permite a adição desordenada dos parâmetros, a custa de performance.</param>
        /// <returns>O comando para a consulta SQL.</returns>
        public DbCommand GetSqlStringCommandArrayBind(string query, int commandTimeout, int arrayBindCount, bool bindByName)
        {
            DbCommand command = baseGetSqlStringCommand(query, bindByName);
            if (commandTimeout > 0) command.CommandTimeout = commandTimeout;
            if (arrayBindCount > 0) ((OracleCommand)command).ArrayBindCount = arrayBindCount;
            return command;
        }

        /// <summary>
        /// Cria um comando do tipo ArrayBind, a partir de uma stored procedure.
        /// </summary>
        /// <param nome="storedProcedureName">O nome da stored procedure.</param>
        /// <param nome="arrayBindCount">O número de vezes que será feita a execução, 
        /// ou seja, a dimensão dos parâmetros do comando.</param>
        /// <returns>O comando para a execução a partir da stored procedure.</returns>
        public DbCommand GetStoredProcCommandArrayBind(string storedProcedureName, int arrayBindCount)
        {
            return GetStoredProcCommandArrayBind(storedProcedureName, 0, arrayBindCount, true);
        }

        /// <summary>
        /// Cria um comando do tipo ArrayBind, a partir de uma stored procedure.
        /// </summary>
        /// <param nome="storedProcedureName">O nome da stored procedure.</param>
        /// <param nome="commandTimeout">O Timeout utilizado na execução do comando.</param>
        /// <param nome="arrayBindCount">O número de vezes que será feita a execução, 
        /// ou seja, a dimensão dos parâmetros do comando.</param>
        /// <returns>O comando para a execução a partir da stored procedure.</returns>
        public DbCommand GetStoredProcCommandArrayBind(string storedProcedureName, int commandTimeout, int arrayBindCount)
        {
            return GetStoredProcCommandArrayBind(storedProcedureName, commandTimeout, arrayBindCount, true);
        }

        /// <summary>
        /// Cria um comando do tipo ArrayBind, a partir de uma stored procedure.
        /// </summary>
        /// <param nome="storedProcedureName">O nome da stored procedure.</param>
        /// <param nome="commandTimeout">O Timeout utilizado na execução do comando.</param>
        /// <param nome="arrayBindCount">O número de vezes que será feita a execução, 
        /// ou seja, a dimensão dos parâmetros do comando.</param>
        /// <param nome="bindByName">Permite a adição desordenada dos parâmetros, a custa de performance.</param>
        /// <returns>O comando para a execução a partir da stored procedure.</returns>
        public DbCommand GetStoredProcCommandArrayBind(string storedProcedureName, int commandTimeout, int arrayBindCount, bool bindByName)
        {
            DbCommand command = baseGetStoredProcCommand(storedProcedureName, bindByName);
            if (commandTimeout > 0) command.CommandTimeout = commandTimeout;
            if (arrayBindCount > 0) ((OracleCommand)command).ArrayBindCount = arrayBindCount;
            return command;
        }

        #endregion

        #region DbCommand base

        private DbCommand baseGetSqlStringCommand(string query, bool bindByName)
        {
            DbCommand command = base.GetSqlStringCommand(query);
            ((OracleCommand)command).BindByName = bindByName;
            return command;
        }

        private DbCommand baseGetStoredProcCommand(string storedProcedureName, bool bindByName)
        {
            DbCommand command = base.GetStoredProcCommand(storedProcedureName);
            ((OracleCommand)command).BindByName = bindByName;
            return command;
        }

        private DbCommand baseGetStoredProcCommand(string storedProcedureName, bool bindByName, params object[] parameterValues)
        {
            DbCommand command = base.GetStoredProcCommand(storedProcedureName, parameterValues);
            ((OracleCommand)command).BindByName = bindByName;
            return command;
        }

        private DbCommand baseGetStoredProcCommandWithSourceColumns(string storedProcedureName, string[] sourcesColumns, bool bindByName)
        {
            DbCommand command = base.GetStoredProcCommandWithSourceColumns(storedProcedureName, sourcesColumns);
            ((OracleCommand)command).BindByName = bindByName;
            return command;
        }

        #endregion

        #region ODP

        /// <summary>
        /// Adiciona um novo parâmetro <see cref="OracleParameter"/> no comando.
        /// Os parâmetros devem estar na order que aparecem na definição da stored procedure.
        /// </summary>
        /// <param nome="command">O comando <see cref="OracleCommand"/> para adicionar o parâmetro.</param>
        /// <param nome="name">O nome do parâmetro.</param>
        /// <param nome="oracleType">O tipo do parâmetro <see cref="OracleDbType"/>.</param>
        /// <param nome="size">O tamanho máximo da coluna.</param>
        /// <param nome="direction">A direção do parâmetro.</param>
        /// <param nome="nullable">Indica se o parâmetro pode ser nulo.</param>
        /// <param nome="precision">O número máximo de digitos utilizado para representar o valor.</param>
        /// <param nome="scale">O número de casas decimais do valor.</param>
        /// <param nome="sourceColumn">O nome da coluna mapeado para o DataSet e
        /// utilizado para carregar ou retornar o valor.</param>
        /// <param nome="sourceVersion">A versão do DataSet que será utilizada.</param>
        /// <param nome="value">O valor do parâmetro.</param>
        internal void AddParameter(OracleCommand command, string name, OracleDbType oracleType, int size,
            ParameterDirection direction, bool nullable, byte precision, byte scale, string sourceColumn,
            DataRowVersion sourceVersion, object value)
        {
            if (command == null) throw new ArgumentNullException("command");

            OracleParameter param = CreateParameter(name, DbType.AnsiString, size, direction, nullable, precision, scale, sourceColumn, sourceVersion, value) as OracleParameter;
            if (oracleType == OracleDbType.RefCursor)
                param.OracleDbType = oracleType;
            else
                param.DbType = (DbType)oracleType;
            command.Parameters.Add(param);
        }

        /// <summary>
        /// Adiciona um novo parâmetro no comando para recuperar o valor retornado por uma Function Oracle.
        /// Este parâmetro deve ser o primeiro parâmetro do comando.
        /// Os parâmetros devem estar na order que aparecem na definição da stored procedure.
        /// </summary>
        /// <param nome="command">O comando para adicionar o parâmetro.</param>
        /// <param nome="name">O nome do parâmetro.</param>
        /// <param nome="dbType">O tipo do parâmetro.</param>
        /// <param nome="size">O tamanho máximo da coluna.</param>
        /// <param nome="direction">A direção do parâmetro.</param>
        /// <param nome="nullable">Indica se o parâmetro pode ser nulo.</param>
        /// <param nome="precision">O número máximo de digitos utilizado para representar o valor.</param>
        /// <param nome="scale">O número de casas decimais do valor.</param>
        /// <param nome="sourceColumn">O nome da coluna mapeado para o DataSet e
        /// utilizado para carregar ou retornar o valor.</param>
        /// <param nome="sourceVersion">A versão do DataSet que será utilizada.</param>
        public void AddReturnValueParameter(DbCommand command, string name, DbType dbType, int size,
                                            bool nullable, byte precision, byte scale, string sourceColumn,
                                            DataRowVersion sourceVersion)
        {
            DbParameter param = CreateParameter(name, DbType.AnsiString, size, ParameterDirection.ReturnValue, nullable, precision, scale, sourceColumn, sourceVersion, Convert.DBNull);
            param.DbType = dbType;
            command.Parameters.Add(param);
        }
        /// <summary>
        /// Adiciona um novo parâmetro no comando para recuperar o valor retornado por uma Function Oracle.
        /// Este parâmetro deve ser o primeiro parâmetro do comando.
        /// Os parâmetros devem estar na order que aparecem na definição da stored procedure.
        /// </summary>
        /// <param nome="command">O comando para adicionar o parâmetro.</param>
        /// <param nome="name">O nome do parâmetro.</param>
        /// <param nome="dbType">O tipo do parâmetro.</param>
        /// <param nome="size">O tamanho máximo da coluna.</param>
        public void AddReturnValueParameter(DbCommand command, string name, DbType dbType, int size)
        {
            AddParameter(command as OracleCommand, name, dbType, size, ParameterDirection.ReturnValue, true, 0, 0, String.Empty, DataRowVersion.Default, Convert.DBNull);
        }

        /// <summary>
        /// Adiciona um parâmetro de entrada no comando do tipo Ref Cursor.
        /// Os parâmetros devem estar na order que aparecem na definição da stored procedure.
        /// </summary>
        /// <param nome="command">O comando para adicionar o parâmetro.</param>
        /// <param nome="RefCursorName">O nome do parâmetro Ref Cursor.</param>
        public void AddCursorInParameter(DbCommand command, string RefCursorName)
        {
            AddParameter(command as OracleCommand, RefCursorName, OracleDbType.RefCursor, 0,
                         ParameterDirection.Input, true, 0, 0, String.Empty,
                         DataRowVersion.Default, Convert.DBNull);
        }

        /// <summary>
        /// Adiciona um parâmetro de entrada e saída no comando do tipo Ref Cursor.
        /// Os parâmetros devem estar na order que aparecem na definição da stored procedure.
        /// </summary>
        /// <param nome="command">O comando para adicionar o parâmetro.</param>
        /// <param nome="RefCursorName">O nome do parâmetro Ref Cursor.</param>
        public void AddCursorInputOutputParameter(DbCommand command, string RefCursorName)
        {
            AddParameter(command as OracleCommand, RefCursorName, OracleDbType.RefCursor, 0,
                         ParameterDirection.InputOutput, true, 0, 0, String.Empty,
                         DataRowVersion.Default, Convert.DBNull);
        }

        /// <summary>
        /// Adiciona um parâmetro de saída no comando do tipo Ref Cursor.
        /// Os parâmetros devem estar na order que aparecem na definição da stored procedure.
        /// </summary>
        /// <param nome="command">O comando para adicionar o parâmetro.</param>
        /// <param nome="RefCursorName">O nome do parâmetro Ref Cursor.</param>
        public void AddCursorOutParameter(DbCommand command, string RefCursorName)
        {
            AddParameter(command as OracleCommand, RefCursorName, OracleDbType.RefCursor, 0,
                         ParameterDirection.Output, true, 0, 0, String.Empty,
                         DataRowVersion.Default, Convert.DBNull);
        }

        /// <summary>
        /// Adiciona um parâmetro no comando do tipo Ref Cursor
        /// para recuperar o retorno de uma Function Oracle.
        /// Este parâmetro deve ser o primeiro parâmetro.
        /// </summary>
        /// <param nome="command">O comando para adicionar o parâmetro.</param>
        /// <param RefCursorName="name">O nome do parâmetro Ref Cursor.</param>
        public void AddCursorReturnValueParameter(DbCommand command, string RefCursorName)
        {
            AddParameter(command as OracleCommand, RefCursorName, OracleDbType.RefCursor, 0,
                         ParameterDirection.ReturnValue, true, 0, 0, String.Empty,
                         DataRowVersion.Default, Convert.DBNull);
        }

        /// <summary>
        /// Adiciona um parâmetro de saída no comando do tipo Ref Cursor.
        /// Os parâmetros devem estar na order que aparecem na definição da stored procedure.
        /// </summary>
        /// <param nome="command">O comando para adicionar o parâmetro.</param>
        /// <param nome="cursorOutName">O nome do parâmetro Ref Cursor.</param>
        public void AddOutParameterRefCursor(DbCommand command, string cursorOutName)
        {
            OracleParameter parRet = new OracleParameter();
            parRet.Direction = ParameterDirection.Output;
            parRet.OracleDbType = OracleDbType.RefCursor;
            parRet.ParameterName = cursorOutName;
            command.Parameters.Add(parRet);
        }

        /// <summary>
        /// Cria um comando a partir de expressão SQL.
        /// </summary>
        /// <param nome="query">O texto da expressão SQL.</param>
        /// <returns>O comando para a consulta SQL.</returns>
        public new DbCommand GetSqlStringCommand(string query)
        {
            return GetSqlStringCommand(query, 0, true);
        }

        /// <summary>
        /// Cria um comando a partir de expressão SQL.
        /// </summary>
        /// <param nome="query">O texto da expressão SQL.</param>
        /// <param nome="commandTimeout">O Timeout utilizado na execução do comando.</param>
        /// <returns>O comando para a consulta SQL.</returns>
        public DbCommand GetSqlStringCommand(string query, int commandTimeout)
        {
            return GetSqlStringCommand(query, commandTimeout, true);
        }

        /// <summary>
        /// Cria um comando a partir de expressão SQL.
        /// </summary>
        /// <param nome="query">O texto da expressão SQL.</param>
        /// <param nome="commandTimeout">O Timeout utilizado na execução do comando.</param>
        /// <param nome="bindByName">Permite a adição desordenada dos parâmetros, a custa de performance.</param>
        /// <returns>O comando para a consulta SQL.</returns>
        public DbCommand GetSqlStringCommand(string query, int commandTimeout, bool bindByName)
        {
            DbCommand command = baseGetSqlStringCommand(query, bindByName);
            if (commandTimeout > 0) command.CommandTimeout = commandTimeout;
            return command;
        }

        /// <summary>
        /// Cria uma instância da classe <see cref="DbCommand"/> para uma stored procedure.
        /// Os parâmetros devem estar na order que aparecem na definição da stored procedure.
        /// </summary>
        /// <param nome="storedProcedureName">O nome da stored procedure.</param>
        /// <returns>O comando para a stored procedure.</returns>
        /// <remarks><para>Os parâmetros da stored procedure irão ser descobertos e 
        /// seus valores serão associados na ordem posicional.</para></remarks>  
        internal OracleCommand GetStoreProcedureOracleCommand(string storedProcedureName)
        {
            OracleCommand command = new OracleCommand(storedProcedureName);
            command.CommandType = CommandType.StoredProcedure;
            // command.CommandTimeout = _commandTimeout;  // Atribuido pela rotina chamadora.
            return command;
        }

        /// <summary>
        /// Cria uma instância da classe <see cref="DbCommand"/> para uma stored procedure.
        /// Os parâmetros devem estar na order que aparecem na definição da stored procedure.
        /// </summary>
        /// <param nome="storedProcedureName">O nome da stored procedure.</param>
        /// <param nome="commandTimeout">O Timeout utilizado na execução do comando.</param>
        /// <returns>O comando para a stored procedure.</returns>
        /// <remarks><para>Os parâmetros da stored procedure irão ser descobertos e 
        /// seus valores serão associados na ordem posicional.</para></remarks>  
        internal OracleCommand GetStoreProcedureCommand(string storedProcedureName, int commandTimeout)
        {
            OracleCommand command = new OracleCommand(storedProcedureName);
            command.CommandType = CommandType.StoredProcedure;
            command.CommandTimeout = commandTimeout;
            return command;
        }

        /// <summary>
        /// Cria um comando sobre uma Sub-Rotina e preenche as propriedades fixas de seus parâmetros.
        /// </summary>
        /// <param nome="storedProcedureName">O nome da Sub-Rotina.</param>
        /// <returns>O comando sobre uma Sub-Rotina.</returns>
        public DbCommand GetDeriveParametersCommand(string storedProcedureName)
        {
            return GetDeriveParametersCommand(storedProcedureName, 0, true);
        }

        /// <summary>
        /// Cria um comando sobre uma Sub-Rotina e preenche as propriedades fixas de seus parâmetros.
        /// </summary>
        /// <param nome="storedProcedureName">O nome da Sub-Rotina.</param>
        /// <param nome="commandTimeout">O Timeout utilizado na execução do comando.</param>
        /// <returns>O comando sobre uma Sub-Rotina.</returns>
        public DbCommand GetDeriveParametersCommand(string storedProcedureName, int commandTimeout)
        {
            return GetDeriveParametersCommand(storedProcedureName, commandTimeout, true);
        }

        /// <summary>
        /// Cria um comando sobre uma Sub-Rotina e preenche as propriedades fixas de seus parâmetros.
        /// </summary>
        /// <param nome="storedProcedureName">O nome da Sub-Rotina.</param>
        /// <param nome="commandTimeout">O Timeout utilizado na execução do comando.</param>
        /// <param nome="bindByName">Permite a adição desordenada dos parâmetros, a custa de performance.</param>
        /// <returns>O comando sobre uma Sub-Rotina.</returns>
        public DbCommand GetDeriveParametersCommand(string storedProcedureName, int commandTimeout, bool bindByName)
        {
            DbCommand command = baseGetStoredProcCommand(storedProcedureName, bindByName);

            if (commandTimeout > 0)
            {
                command.CommandTimeout = commandTimeout;
            }

            CheckIfOracleCommand(command);

            using (DatabaseConnectionWrapper wrapper = this.GetOpenConnection())
            {
                PrepareCommand(command, wrapper.Connection);
                OracleCommandBuilder.DeriveParameters((OracleCommand)command);
            }

            return command;
        }

        /// <summary>
        /// Retorna o valor de um Function Oracle.
        /// </summary>
        /// <param nome="command">O Comando da Function Oracle.</param>
        /// <returns>Retorna o valor de um Function Oracle.</returns>
        public object GetFunctionReturnValue(DbCommand command)
        {
            return command.Parameters[0].Value;
        }

        /// <summary>
        /// Retorna o valor de um PL/SQL AssociativeArray pelo índice do parâmetro.
        /// </summary>
        /// <param nome="command">O Comando que contem o PL/SQL AssociativeArray.</param>
        /// <param name="ParameterIndex">O índice do parâmetro.</para></param>
        /// <returns>Retorna um PL/SQL AssociativeArray.</returns>
        public Array GetParameterAssociativeArray(DbCommand command, int ParameterIndex)
        {
            return (command.Parameters[ParameterIndex].Value as Array);
        }

        /// <summary>
        /// Retorna o valor de um PL/SQL AssociativeArray pelo nome do parâmetro.
        /// </summary>
        /// <param nome="command">O Comando que contem o PL/SQL AssociativeArray.</param>
        /// <param name="ParameterName">O nome do parâmetro.</para></param>
        /// <returns>Retorna um PL/SQL AssociativeArray.</returns>
        public Array GetParameterAssociativeArray(DbCommand command, string ParameterName)
        {
            return (command.Parameters[ParameterName].Value as Array);
        }

        /// <summary>
        /// Executa o comando que teve seus parâmetros criados pelo método 
        /// GetDeriveParametersCommand  e retorna o número de linhas afetadas.
        /// </summary>
        /// <param nome="command">O comando a ser executado.</param>
        /// <returns>O número de linhas afetadas.</returns>
        public int ExecuteNonQueryDeriveParameters(DbCommand command)
        {
            int int_return = -1;
            int size = auto_size;
            bool bln = false;

            SetArraySize(command as OracleCommand, size);

            while (!bln)
            {
                try
                {
                    int_return = command.ExecuteNonQuery();
                    bln = true;
                }
                catch (OracleException oe)
                {
                    if (oe.Number == 6513)
                    {
                        size *= 1024;
                        SetArraySize(command as OracleCommand, size);
                    }
                    else
                    {
                        bln = true;
                        throw oe;
                    }
                }
            }

            return int_return;
        }

        /// <summary>
        /// Executa o comando que teve seus parâmetros criados pelo método 
        /// GetDeriveParametersCommand  e retorna uma linha do comando.
        /// </summary>
        /// <param nome="command">O comando a ser executado.</param>
        /// <returns>A primeira linhas retornada pelo comando.</returns>
        public object ExecuteScalarDeriveParameters(DbCommand command)
        {
            object obj_return = null;
            int size = auto_size;
            bool bln = false;

            SetArraySize(command as OracleCommand, size);

            while (!bln)
            {
                try
                {
                    obj_return = command.ExecuteScalar();
                    bln = true;
                }
                catch (OracleException oe)
                {
                    if (oe.Number == 6513)
                    {
                        size *= 1024;
                        SetArraySize(command as OracleCommand, size);
                    }
                    else
                    {
                        bln = true;
                        throw oe;
                    }
                }
            }

            return obj_return;
        }

        /// <summary>
        /// Livra a memória utilizada pelos parâmetros do comando <see cref="DbCommand"/>. 
        /// </summary>
        /// <param name="command">O <see cref="DbCommand"/> comando.</param>
        /// <remarks>
        /// Importante utilizá-lo quando usar parâmetros do tipo Ref Cursor ou PL/SQL AssociativeArray.
        /// </remarks>
        public void DisposeParameters(DbCommand command)
        {
            foreach (OracleParameter op in command.Parameters) op.Dispose();
        }

        /// <summary>
        /// Cria um <see cref="DbDataReader"/> baseado no comando <paramref name="command"/> 
        /// para o parâmentro Ref Cursor de índice Index.
        /// </summary>
        /// <param name="command">O comando a ser executado.</param>        
        /// <param name="Index">Índice do parâmentro Ref Cursor.</param>
        /// <returns>Um objeto <see cref="DbDataReader"/>.</returns>        
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="command"/> não pode ser <see langword="null"/> (Nothing em Visual Basic).</para>
        /// </exception>
        public DbDataReader GetDataReaderFromRefCursor(DbCommand command, int index)
        {
            if (command.Parameters[index].DbType != DbType.Object)
                throw (new Exception("Parametro não é um cursor."));

            return (OracleDataReader)command.Parameters[index].Value;
        }

        /// <summary>
        /// Cria um <see cref="DbDataReader"/> baseado no comando <paramref name="command"/> 
        /// para o parâmentro Ref Cursor de índice Index.
        /// </summary>
        /// <param name="command">O comando a ser executado.</param>        
        /// <param name="RefCursorName">Nome do parâmentro Ref Cursor.</param>
        /// <returns>Um objeto <see cref="DbDataReader"/>.</returns>        
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="command"/> não pode ser <see langword="null"/> (Nothing em Visual Basic).</para>
        /// </exception>
        public DbDataReader GetDataReaderFromRefCursor(DbCommand command, string refCursorName)
        {
            if (command.Parameters[refCursorName].DbType != DbType.Object)
                throw (new Exception("Parametro não é um cursor."));

            return (OracleDataReader)command.Parameters[refCursorName].Value;
        }

        #endregion

        #region Public Static Methods

        /// <summary>
        /// Fecha o pooling de conexões.
        /// </summary>
        public static void ClearAllPools()
        {
            OracleConnection.ClearAllPools();
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Livra a memória utilizada pela classe <see cref="OracleDataBase"/>. 
        /// </summary>
        /// <remarks>
        /// Importante utilizá-lo para liberar as conexões Oracle.
        /// </remarks>
        public void Dispose()
        {
            OracleConnection.ClearAllPools();
        }

        #endregion

    }
}
